﻿// *****************************************************************************
// **                   Ladenius MediaCenter TVSeries                         **
// ** ----------------------------------------------------------------------- **
// ** (c) 2008 by Jurjen Ladenius                                             **
// *****************************************************************************

#region Using directives
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Ladenius.Series.Path.Business;
using System.Collections.ObjectModel;
using System.Data;
using Ladenius.Series.Path.Entities;
using Effectory.Utilities;
using System.ComponentModel;
#endregion

namespace Ladenius.Series.Path {
    /// <summary>
    /// Provides entity based access to Series in the file system
    /// </summary>
    public class PathManager {

        #region Fields
        private List<string> _basePaths;
        private List<ParsingRule> _parsingRules;
        private DataTable _files;
        private List<ParsedFile> _parsedFiles;
        private BackgroundWorker _backgroundWorker;
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="PathManager"/> class.
        /// </summary>
        /// <param name="basePaths">The base paths.</param>
        public PathManager(List<string> basePaths, List<string> parsingRules) {
            // Set base paths
            _basePaths = basePaths;
            CheckBasePaths();
            // Prepare parsing rules regular expressions
            _parsingRules = new List<ParsingRule>();
            SetParsingRules(parsingRules);
            // Create empty datatable 
            _files = new DataTable("Files");
            _files.Columns.Add("Series", typeof(string));
            _files.Columns.Add("SeasonNumber", typeof(int));
            _files.Columns.Add("EpisodeNumber", typeof(int));
            _files.Columns.Add("EpisodeName", typeof(string));
            _files.Columns.Add("Path", typeof(string));
            _files.Clear();
            // Prepare other vars
            _parsedFiles = new List<ParsedFile>();
            _backgroundWorker = new BackgroundWorker();
            _backgroundWorker.WorkerReportsProgress = true;
            _backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
            _backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(_backgroundWorker_ProgressChanged);
            _backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
        }

        void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
            RunParse();
        }

        #endregion

        #region BackgroundWorker Events
        /// <summary>
        /// Handles the ProgressChanged event of the _backgroundWorker control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.ProgressChangedEventArgs"/> instance containing the event data.</param>
        private void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
            OnReportProgress(e.ProgressPercentage, e.UserState.ToString());
        }
        /// <summary>
        /// Handles the RunWorkerCompleted event of the _backgroundWorker control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs"/> instance containing the event data.</param>
        private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            OnCompleted();
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the base paths.
        /// </summary>
        /// <value>The base paths.</value>
        public ReadOnlyCollection<string> BasePaths {
            get { return _basePaths.AsReadOnly(); }
        }
        /// <summary>
        /// Gets the parsing rules.
        /// </summary>
        /// <value>The parsing rules.</value>
        internal ReadOnlyCollection<ParsingRule> ParsingRules {
            get { return _parsingRules.AsReadOnly(); }
        }
        /// <summary>
        /// Gets the parsed files.
        /// </summary>
        /// <value>The parsed files.</value>
        public ReadOnlyCollection<ParsedFile> ParsedFiles {
            get { return _parsedFiles.AsReadOnly(); }
        }
        #endregion

        #region Events
        public delegate void PathProgressEventHandler(object sender, PathProgressEventArgs e);
        /// <summary>
        /// Occurs when reporting progress.
        /// </summary>
        public event PathProgressEventHandler ReportProgress;

        /// <summary>
        /// Called when reporting progress.
        /// </summary>
        /// <param name="percentage">The percentage.</param>
        /// <param name="message">The message.</param>
        protected virtual void OnReportProgress(int percentage, string message) {
            PathProgressEventHandler handler = ReportProgress;
            if (handler != null)
                handler(this, new PathProgressEventArgs(percentage, message));
        }

        public delegate void CompletedEventHandler (object sender);
        /// <summary>
        /// Occurs when parsing is completed.
        /// </summary>
        public event CompletedEventHandler Completed;
        /// <summary>
        /// Called when parsing is completed.
        /// </summary>
        protected virtual void OnCompleted() {
            CompletedEventHandler handler = Completed;
            if (handler != null)
                handler(this);
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Parses the basepaths according to the parsingrules asynchronally.
        /// </summary>
        public void Parse() {
            _backgroundWorker.RunWorkerAsync();
        }

        #endregion

        #region Private methods
        /// <summary>
        /// Runs the parse.
        /// </summary>
        private void RunParse() {

            _parsedFiles.Clear();

            _backgroundWorker.ReportProgress(0, "Start looking for files");

            // Retrieve a list of files from the filesystem
            int percentageStep = 100 / BasePaths.Count;
            int currentStep = 0;
            List<string> files;
            foreach (string basePath in BasePaths) {
                files = FileSystemManager.GetFiles(basePath);
                _backgroundWorker.ReportProgress(currentStep, string.Format("Found {0} files in base path '{1}'", files.Count.ToString(), basePath));
                ParseFiles(files, percentageStep, currentStep);
                currentStep += percentageStep;
                _backgroundWorker.ReportProgress(currentStep, string.Format("Until now found and parsed {0} files", _parsedFiles.Count.ToString()));

            }

            _backgroundWorker.ReportProgress(100, string.Format("Succesfully found and parsed a total of {0} files", _parsedFiles.Count.ToString()));

        }

        /// <summary>
        /// Parses the files.
        /// </summary>
        /// <param name="fileNames">The file names.</param>
        /// <param name="percentageSeed">The percentage seed.</param>
        /// <param name="step">The step.</param>
        private void ParseFiles(List<string> fileNames, int percentageSeed, int step) {
            // Perform parsing rules on each of the file names
            string filename;
            bool isMatch;
            int percentageStep = percentageSeed / fileNames.Count;
            int currentStep = step;
            foreach (string fullpath in fileNames) {

                filename = fullpath;

                // remove base path
                foreach (string basePath in BasePaths) {
                    if (filename.ToLower().StartsWith(basePath.ToLower())) {
                        filename = filename.Substring(basePath.Length);
                        break;
                    }
                }
                // remove leading backslash
                if (filename.StartsWith(@"\")) filename = filename.Substring(1);
                // perform parsing
                isMatch = false;
                foreach (ParsingRule parsingRule in ParsingRules) {
                    if (!isMatch && parsingRule.RegularExpression.IsMatch(filename)) {
                        AddFile(filename, fullpath, parsingRule);
                        isMatch = true;
                        break;
                    }
                }
                if (!isMatch)
                    _backgroundWorker.ReportProgress(currentStep, string.Format("Could not parse file '{0}'", fullpath));

                currentStep += percentageStep;
            }

        }

        /// <summary>
        /// Checks the base paths.
        /// </summary>
        private void CheckBasePaths() {
            // TODO: Check base paths for nesting. Remove accordingly.
        }
        /// <summary>
        /// Prepares the parsing rule regular expressions.
        /// </summary>
        private void SetParsingRules(List<string> parsingRules) {
            _parsingRules.Clear();
            foreach (string parsingRule in parsingRules) {
                _parsingRules.Add (new ParsingRule(parsingRule));
            }
        }
        /// <summary>
        /// Adds the file to the parsed file collection.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <param name="fullpath">The fullpath.</param>
        /// <param name="parsingRule">The parsing rule.</param>
        private void AddFile(string filename, string fullpath, ParsingRule parsingRule) {
            List<string> properties = parsingRule.RegularExpression.Split(filename).Where<string>(d => d != string.Empty).ToList<string>();
            int i = 0;
            DataRow row = _files.NewRow();;
            foreach (string key in parsingRule.Keys) {
                row[key] = properties[i];
                i++;
            }
            row["path"] = fullpath;
            _files.Rows.Add(row);

            _parsedFiles.Add(new ParsedFile() {
                EpisodeName = SqlUtilities.FromDBValue<string>(row, "EpisodeName"),
                EpisodeNumber = SqlUtilities.FromDBValue<int>(row, "EpisodeNumber"),
                FileName = SqlUtilities.FromDBValue<string>(row, "Path"),
                SeasonNumber = SqlUtilities.FromDBValue<int>(row, "SeasonNumber"),
                Series = SqlUtilities.FromDBValue<string>(row, "Series")
            });
        }

        #endregion
    }
}
